package com.telecom.oil.auth.support.core;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.telecom.oil.common.security.wechat.WechatAuthenticationToken;
import com.telecom.oil.common.core.util.WebUtils;
import com.telecom.oil.common.security.service.OilUserDetailsService;
import lombok.SneakyThrows;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.core.Ordered;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.*;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.authentication.www.BasicAuthenticationConverter;
import org.springframework.util.Assert;

import javax.servlet.http.HttpServletRequest;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;

/**
 * @author jianghuai
 * @date 2022-12-12
 */
public class OilWechatDaoAuthenticationProvider
		implements AuthenticationProvider, InitializingBean, MessageSourceAware {

	protected final Log logger = LogFactory.getLog(this.getClass());

	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

	private UserCache userCache = new NullUserCache();

	private UserDetailsChecker preAuthenticationChecks = new OilWechatDaoAuthenticationProvider.DefaultPreAuthenticationChecks();

	private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

	private final static BasicAuthenticationConverter basicConvert = new BasicAuthenticationConverter();

	private UserDetailsService userDetailsService;

	@SneakyThrows
	protected final UserDetails retrieveUser(String code, WechatAuthenticationToken authentication) {
		HttpServletRequest request = WebUtils.getRequest().orElseThrow(
				(Supplier<Throwable>) () -> new InternalAuthenticationServiceException("web request is empty"));

		Map<String, String> paramMap = ServletUtil.getParamMap(request);
		String grantType = paramMap.get(OAuth2ParameterNames.GRANT_TYPE);
		String clientId = paramMap.get(OAuth2ParameterNames.CLIENT_ID);

		if (StrUtil.isBlank(clientId)) {
			clientId = basicConvert.convert(request).getName();
		}

		Map<String, OilUserDetailsService> userDetailsServiceMap = SpringUtil
				.getBeansOfType(OilUserDetailsService.class);

		String finalClientId = clientId;
		Optional<OilUserDetailsService> optional = userDetailsServiceMap.values().stream()
				.filter(service -> service.support(finalClientId, grantType))
				.max(Comparator.comparingInt(Ordered::getOrder));

		if (!optional.isPresent()) {
			throw new InternalAuthenticationServiceException("UserDetailsService error , not register");
		}

		try {
			UserDetails loadedUser = optional.get().loadUserByCode(code);
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException(
						"UserDetailsService returned null, which is an interface contract violation");
			}
			return loadedUser;
		}
		catch (InternalAuthenticationServiceException ex) {
			throw ex;
		}
		catch (Exception ex) {
			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
		}
	}

	protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
			UserDetails user) {
		WechatAuthenticationToken result = WechatAuthenticationToken.authenticated(principal,
				authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
		result.setDetails(authentication.getDetails());
		this.logger.debug("Authenticated user");
		return result;
	}

	public void setUserDetailsService(UserDetailsService userDetailsService) {
		this.userDetailsService = userDetailsService;
	}

	protected UserDetailsService getUserDetailsService() {
		return this.userDetailsService;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		Assert.notNull(this.userCache, "A user cache must be set");
		Assert.notNull(this.messages, "A message source must be set");
		this.doAfterPropertiesSet();
	}

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Assert.isInstanceOf(WechatAuthenticationToken.class, authentication, () -> {
			return this.messages.getMessage("OilWechatDaoAuthenticationProvider.onlySupports",
					"Only WechatAuthenticationToken is supported");
		});
		String code = this.determineCode(authentication);
		UserDetails user = null;
		try {
			user = this.retrieveUser(code, (WechatAuthenticationToken) authentication);
		}
		catch (Exception var6) {
			this.logger.debug("Failed to execute '" + code + "'");
			throw var6;
		}

		Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");

		try {
			this.preAuthenticationChecks.check(user);
		}
		catch (AuthenticationException var7) {
			throw var7;
		}

		Object principalToReturn = user;

		return this.createSuccessAuthentication(principalToReturn, authentication, user);
	}

	@Override
	public boolean supports(Class<?> authentication) {
		return WechatAuthenticationToken.class.isAssignableFrom(authentication);
	}

	protected void doAfterPropertiesSet() throws Exception {
	}

	private String determineCode(Authentication authentication) {
		return authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
	}

	@Override
	public void setMessageSource(MessageSource messageSource) {
		this.messages = new MessageSourceAccessor(messageSource);
	}

	private class DefaultPreAuthenticationChecks implements UserDetailsChecker {

		private DefaultPreAuthenticationChecks() {
		}

		public void check(UserDetails user) {
			if (!user.isAccountNonLocked()) {
				OilWechatDaoAuthenticationProvider.this.logger
						.debug("Failed to authenticate since user account is locked");
				throw new LockedException(OilWechatDaoAuthenticationProvider.this.messages
						.getMessage("OilWechatDaoAuthenticationProvider.locked", "User account is locked"));
			}
			else if (!user.isEnabled()) {
				OilWechatDaoAuthenticationProvider.this.logger
						.debug("Failed to authenticate since user account is disabled");
				throw new DisabledException(OilWechatDaoAuthenticationProvider.this.messages
						.getMessage("OilWechatDaoAuthenticationProvider.disabled", "User is disabled"));
			}
			else if (!user.isAccountNonExpired()) {
				OilWechatDaoAuthenticationProvider.this.logger
						.debug("Failed to authenticate since user account has expired");
				throw new AccountExpiredException(OilWechatDaoAuthenticationProvider.this.messages
						.getMessage("OilWechatDaoAuthenticationProvider.expired", "User account has expired"));
			}
		}

	}

}
